/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package io.netty.buffer;

import io.netty.util.ByteProcessor;
import io.netty.util.CharsetUtil;
import io.netty.util.IllegalReferenceCountException;
import io.netty.util.ResourceLeakDetector;
import io.netty.util.ResourceLeakDetectorFactory;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ScatteringByteChannel;
import java.nio.charset.Charset;

import static io.netty.util.internal.MathUtil.isOutOfBounds;

/**
 * A skeletal implementation of a buffer.
 */
public abstract class AbstractByteBuf extends ByteBuf
{
    private static final InternalLogger logger = InternalLoggerFactory
            .getInstance(AbstractByteBuf.class);

    private static final String PROP_MODE = "io.netty.buffer.bytebuf.checkAccessible";

    private static final boolean checkAccessible;

    static
    {
        checkAccessible = SystemPropertyUtil.getBoolean(PROP_MODE, true);
        if (logger.isDebugEnabled())
        {
            logger.debug("-D{}: {}", PROP_MODE, checkAccessible);
        }
    }

    static final ResourceLeakDetector<ByteBuf> leakDetector = ResourceLeakDetectorFactory
            .instance().newResourceLeakDetector(ByteBuf.class);

    int readerIndex;

    int writerIndex;

    private int markedReaderIndex;

    private int markedWriterIndex;

    private int maxCapacity;

    protected AbstractByteBuf(int maxCapacity)
    {
        if (maxCapacity < 0)
        {
            throw new IllegalArgumentException(
                    "maxCapacity: " + maxCapacity + " (expected: >= 0)");
        }
        this.maxCapacity = maxCapacity;
    }

    @Override
    public boolean isReadOnly()
    {
        return false;
    }

    @SuppressWarnings("deprecation")
    @Override
    public ByteBuf asReadOnly()
    {
        if (isReadOnly())
        {
            return this;
        }
        return Unpooled.unmodifiableBuffer(this);
    }

    @Override
    public int maxCapacity()
    {
        return maxCapacity;
    }

    protected final void maxCapacity(int maxCapacity)
    {
        this.maxCapacity = maxCapacity;
    }

    @Override
    public int readerIndex()
    {
        return readerIndex;
    }

    @Override
    public ByteBuf readerIndex(int readerIndex)
    {
        if (readerIndex < 0 || readerIndex > writerIndex)
        {
            throw new IndexOutOfBoundsException(String.format(
                    "readerIndex: %d (expected: 0 <= readerIndex <= writerIndex(%d))",
                    readerIndex, writerIndex));
        }
        this.readerIndex = readerIndex;
        return this;
    }

    @Override
    public int writerIndex()
    {
        return writerIndex;
    }

    @Override
    public ByteBuf writerIndex(int writerIndex)
    {
        if (writerIndex < readerIndex || writerIndex > capacity())
        {
            throw new IndexOutOfBoundsException(String.format(
                    "writerIndex: %d (expected: readerIndex(%d) <= writerIndex <= capacity(%d))",
                    writerIndex, readerIndex, capacity()));
        }
        this.writerIndex = writerIndex;
        return this;
    }

    @Override
    public ByteBuf setIndex(int readerIndex, int writerIndex)
    {
        if (readerIndex < 0 || readerIndex > writerIndex
                || writerIndex > capacity())
        {
            throw new IndexOutOfBoundsException(String.format(
                    "readerIndex: %d, writerIndex: %d (expected: 0 <= readerIndex <= writerIndex <= capacity(%d))",
                    readerIndex, writerIndex, capacity()));
        }
        setIndex0(readerIndex, writerIndex);
        return this;
    }

    @Override
    public ByteBuf clear()
    {
        readerIndex = writerIndex = 0;
        return this;
    }

    @Override
    public boolean isReadable()
    {
        return writerIndex > readerIndex;
    }

    @Override
    public boolean isReadable(int numBytes)
    {
        return writerIndex - readerIndex >= numBytes;
    }

    @Override
    public boolean isWritable()
    {
        return capacity() > writerIndex;
    }

    @Override
    public boolean isWritable(int numBytes)
    {
        return capacity() - writerIndex >= numBytes;
    }

    @Override
    public int readableBytes()
    {
        return writerIndex - readerIndex;
    }

    @Override
    public int writableBytes()
    {
        return capacity() - writerIndex;
    }

    @Override
    public int maxWritableBytes()
    {
        return maxCapacity() - writerIndex;
    }

    @Override
    public ByteBuf markReaderIndex()
    {
        markedReaderIndex = readerIndex;
        return this;
    }

    @Override
    public ByteBuf resetReaderIndex()
    {
        readerIndex(markedReaderIndex);
        return this;
    }

    @Override
    public ByteBuf markWriterIndex()
    {
        markedWriterIndex = writerIndex;
        return this;
    }

    @Override
    public ByteBuf resetWriterIndex()
    {
        writerIndex = markedWriterIndex;
        return this;
    }

    @Override
    public ByteBuf discardReadBytes()
    {
        ensureAccessible();
        if (readerIndex == 0)
        {
            return this;
        }

        if (readerIndex != writerIndex)
        {
            setBytes(0, this, readerIndex, writerIndex - readerIndex);
            writerIndex -= readerIndex;
            adjustMarkers(readerIndex);
            readerIndex = 0;
        }
        else
        {
            adjustMarkers(readerIndex);
            writerIndex = readerIndex = 0;
        }
        return this;
    }

    @Override
    public ByteBuf discardSomeReadBytes()
    {
        ensureAccessible();
        if (readerIndex == 0)
        {
            return this;
        }

        if (readerIndex == writerIndex)
        {
            adjustMarkers(readerIndex);
            writerIndex = readerIndex = 0;
            return this;
        }

        if (readerIndex >= capacity() >>> 1)
        {
            setBytes(0, this, readerIndex, writerIndex - readerIndex);
            writerIndex -= readerIndex;
            adjustMarkers(readerIndex);
            readerIndex = 0;
        }
        return this;
    }

    protected final void adjustMarkers(int decrement)
    {
        int markedReaderIndex = this.markedReaderIndex;
        if (markedReaderIndex <= decrement)
        {
            this.markedReaderIndex = 0;
            int markedWriterIndex = this.markedWriterIndex;
            if (markedWriterIndex <= decrement)
            {
                this.markedWriterIndex = 0;
            }
            else
            {
                this.markedWriterIndex = markedWriterIndex - decrement;
            }
        }
        else
        {
            this.markedReaderIndex = markedReaderIndex - decrement;
            markedWriterIndex -= decrement;
        }
    }

    @Override
    public ByteBuf ensureWritable(int minWritableBytes)
    {
        if (minWritableBytes < 0)
        {
            throw new IllegalArgumentException(String.format(
                    "minWritableBytes: %d (expected: >= 0)", minWritableBytes));
        }
        ensureWritable0(minWritableBytes);
        return this;
    }

    private void ensureWritable0(int minWritableBytes)
    {
        if (minWritableBytes <= writableBytes())
        {
            return;
        }

        if (minWritableBytes > maxCapacity - writerIndex)
        {
            throw new IndexOutOfBoundsException(String.format(
                    "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",
                    writerIndex, minWritableBytes, maxCapacity, this));
        }

        // Normalize the current capacity to the power of 2.
        int newCapacity = alloc().calculateNewCapacity(
                writerIndex + minWritableBytes, maxCapacity);

        // Adjust to the new capacity.
        capacity(newCapacity);
    }

    @Override
    public int ensureWritable(int minWritableBytes, boolean force)
    {
        if (minWritableBytes < 0)
        {
            throw new IllegalArgumentException(String.format(
                    "minWritableBytes: %d (expected: >= 0)", minWritableBytes));
        }

        if (minWritableBytes <= writableBytes())
        {
            return 0;
        }

        if (minWritableBytes > maxCapacity - writerIndex)
        {
            if (force)
            {
                if (capacity() == maxCapacity())
                {
                    return 1;
                }

                capacity(maxCapacity());
                return 3;
            }
        }

        // Normalize the current capacity to the power of 2.
        int newCapacity = alloc().calculateNewCapacity(
                writerIndex + minWritableBytes, maxCapacity);

        // Adjust to the new capacity.
        capacity(newCapacity);
        return 2;
    }

    @Override
    public ByteBuf order(ByteOrder endianness)
    {
        if (endianness == null)
        {
            throw new NullPointerException("endianness");
        }
        if (endianness == order())
        {
            return this;
        }
        return newSwappedByteBuf();
    }

    /**
     * Creates a new {@link SwappedByteBuf} for this {@link ByteBuf} instance.
     */
    protected SwappedByteBuf newSwappedByteBuf()
    {
        return new SwappedByteBuf(this);
    }

    @Override
    public byte getByte(int index)
    {
        checkIndex(index);
        return _getByte(index);
    }

    protected abstract byte _getByte(int index);

    @Override
    public boolean getBoolean(int index)
    {
        return getByte(index) != 0;
    }

    @Override
    public short getUnsignedByte(int index)
    {
        return (short) (getByte(index) & 0xFF);
    }

    @Override
    public short getShort(int index)
    {
        checkIndex(index, 2);
        return _getShort(index);
    }

    protected abstract short _getShort(int index);

    @Override
    public short getShortLE(int index)
    {
        checkIndex(index, 2);
        return _getShortLE(index);
    }

    protected abstract short _getShortLE(int index);

    @Override
    public int getUnsignedShort(int index)
    {
        return getShort(index) & 0xFFFF;
    }

    @Override
    public int getUnsignedShortLE(int index)
    {
        return getShortLE(index) & 0xFFFF;
    }

    @Override
    public int getUnsignedMedium(int index)
    {
        checkIndex(index, 3);
        return _getUnsignedMedium(index);
    }

    protected abstract int _getUnsignedMedium(int index);

    @Override
    public int getUnsignedMediumLE(int index)
    {
        checkIndex(index, 3);
        return _getUnsignedMediumLE(index);
    }

    protected abstract int _getUnsignedMediumLE(int index);

    @Override
    public int getMedium(int index)
    {
        int value = getUnsignedMedium(index);
        if ((value & 0x800000) != 0)
        {
            value |= 0xff000000;
        }
        return value;
    }

    @Override
    public int getMediumLE(int index)
    {
        int value = getUnsignedMediumLE(index);
        if ((value & 0x800000) != 0)
        {
            value |= 0xff000000;
        }
        return value;
    }

    @Override
    public int getInt(int index)
    {
        checkIndex(index, 4);
        return _getInt(index);
    }

    protected abstract int _getInt(int index);

    @Override
    public int getIntLE(int index)
    {
        checkIndex(index, 4);
        return _getIntLE(index);
    }

    protected abstract int _getIntLE(int index);

    @Override
    public long getUnsignedInt(int index)
    {
        return getInt(index) & 0xFFFFFFFFL;
    }

    @Override
    public long getUnsignedIntLE(int index)
    {
        return getIntLE(index) & 0xFFFFFFFFL;
    }

    @Override
    public long getLong(int index)
    {
        checkIndex(index, 8);
        return _getLong(index);
    }

    protected abstract long _getLong(int index);

    @Override
    public long getLongLE(int index)
    {
        checkIndex(index, 8);
        return _getLongLE(index);
    }

    protected abstract long _getLongLE(int index);

    @Override
    public char getChar(int index)
    {
        return (char) getShort(index);
    }

    @Override
    public float getFloat(int index)
    {
        return Float.intBitsToFloat(getInt(index));
    }

    @Override
    public double getDouble(int index)
    {
        return Double.longBitsToDouble(getLong(index));
    }

    @Override
    public ByteBuf getBytes(int index, byte[] dst)
    {
        getBytes(index, dst, 0, dst.length);
        return this;
    }

    @Override
    public ByteBuf getBytes(int index, ByteBuf dst)
    {
        getBytes(index, dst, dst.writableBytes());
        return this;
    }

    @Override
    public ByteBuf getBytes(int index, ByteBuf dst, int length)
    {
        getBytes(index, dst, dst.writerIndex(), length);
        dst.writerIndex(dst.writerIndex() + length);
        return this;
    }

    @Override
    public CharSequence getCharSequence(int index, int length, Charset charset)
    {
        // TODO: We could optimize this for UTF8 and US_ASCII
        return toString(index, length, charset);
    }

    @Override
    public CharSequence readCharSequence(int length, Charset charset)
    {
        CharSequence sequence = getCharSequence(readerIndex, length, charset);
        readerIndex += length;
        return sequence;
    }

    @Override
    public ByteBuf setByte(int index, int value)
    {
        checkIndex(index);
        _setByte(index, value);
        return this;
    }

    protected abstract void _setByte(int index, int value);

    @Override
    public ByteBuf setBoolean(int index, boolean value)
    {
        setByte(index, value ? 1 : 0);
        return this;
    }

    @Override
    public ByteBuf setShort(int index, int value)
    {
        checkIndex(index, 2);
        _setShort(index, value);
        return this;
    }

    protected abstract void _setShort(int index, int value);

    @Override
    public ByteBuf setShortLE(int index, int value)
    {
        checkIndex(index, 2);
        _setShortLE(index, value);
        return this;
    }

    protected abstract void _setShortLE(int index, int value);

    @Override
    public ByteBuf setChar(int index, int value)
    {
        setShort(index, value);
        return this;
    }

    @Override
    public ByteBuf setMedium(int index, int value)
    {
        checkIndex(index, 3);
        _setMedium(index, value);
        return this;
    }

    protected abstract void _setMedium(int index, int value);

    @Override
    public ByteBuf setMediumLE(int index, int value)
    {
        checkIndex(index, 3);
        _setMediumLE(index, value);
        return this;
    }

    protected abstract void _setMediumLE(int index, int value);

    @Override
    public ByteBuf setInt(int index, int value)
    {
        checkIndex(index, 4);
        _setInt(index, value);
        return this;
    }

    protected abstract void _setInt(int index, int value);

    @Override
    public ByteBuf setIntLE(int index, int value)
    {
        checkIndex(index, 4);
        _setIntLE(index, value);
        return this;
    }

    protected abstract void _setIntLE(int index, int value);

    @Override
    public ByteBuf setFloat(int index, float value)
    {
        setInt(index, Float.floatToRawIntBits(value));
        return this;
    }

    @Override
    public ByteBuf setLong(int index, long value)
    {
        checkIndex(index, 8);
        _setLong(index, value);
        return this;
    }

    protected abstract void _setLong(int index, long value);

    @Override
    public ByteBuf setLongLE(int index, long value)
    {
        checkIndex(index, 8);
        _setLongLE(index, value);
        return this;
    }

    protected abstract void _setLongLE(int index, long value);

    @Override
    public ByteBuf setDouble(int index, double value)
    {
        setLong(index, Double.doubleToRawLongBits(value));
        return this;
    }

    @Override
    public ByteBuf setBytes(int index, byte[] src)
    {
        setBytes(index, src, 0, src.length);
        return this;
    }

    @Override
    public ByteBuf setBytes(int index, ByteBuf src)
    {
        setBytes(index, src, src.readableBytes());
        return this;
    }

    @Override
    public ByteBuf setBytes(int index, ByteBuf src, int length)
    {
        checkIndex(index, length);
        if (src == null)
        {
            throw new NullPointerException("src");
        }
        if (length > src.readableBytes())
        {
            throw new IndexOutOfBoundsException(String.format(
                    "length(%d) exceeds src.readableBytes(%d) where src is: %s",
                    length, src.readableBytes(), src));
        }

        setBytes(index, src, src.readerIndex(), length);
        src.readerIndex(src.readerIndex() + length);
        return this;
    }

    @Override
    public ByteBuf setZero(int index, int length)
    {
        if (length == 0)
        {
            return this;
        }

        checkIndex(index, length);

        int nLong = length >>> 3;
        int nBytes = length & 7;
        for (int i = nLong; i > 0; i--)
        {
            _setLong(index, 0);
            index += 8;
        }
        if (nBytes == 4)
        {
            _setInt(index, 0);
            // Not need to update the index as we not will use it after this.
        }
        else if (nBytes < 4)
        {
            for (int i = nBytes; i > 0; i--)
            {
                _setByte(index, (byte) 0);
                index++;
            }
        }
        else
        {
            _setInt(index, 0);
            index += 4;
            for (int i = nBytes - 4; i > 0; i--)
            {
                _setByte(index, (byte) 0);
                index++;
            }
        }
        return this;
    }

    @Override
    public int setCharSequence(int index, CharSequence sequence,
            Charset charset)
    {
        if (charset.equals(CharsetUtil.UTF_8))
        {
            ensureWritable(ByteBufUtil.utf8MaxBytes(sequence));
            return ByteBufUtil.writeUtf8(this, index, sequence,
                    sequence.length());
        }
        if (charset.equals(CharsetUtil.US_ASCII))
        {
            int len = sequence.length();
            ensureWritable(len);
            return ByteBufUtil.writeAscii(this, index, sequence, len);
        }
        byte[] bytes = sequence.toString().getBytes(charset);
        ensureWritable(bytes.length);
        setBytes(index, bytes);
        return bytes.length;
    }

    @Override
    public byte readByte()
    {
        checkReadableBytes0(1);
        int i = readerIndex;
        byte b = _getByte(i);
        readerIndex = i + 1;
        return b;
    }

    @Override
    public boolean readBoolean()
    {
        return readByte() != 0;
    }

    @Override
    public short readUnsignedByte()
    {
        return (short) (readByte() & 0xFF);
    }

    @Override
    public short readShort()
    {
        checkReadableBytes0(2);
        short v = _getShort(readerIndex);
        readerIndex += 2;
        return v;
    }

    @Override
    public short readShortLE()
    {
        checkReadableBytes0(2);
        short v = _getShortLE(readerIndex);
        readerIndex += 2;
        return v;
    }

    @Override
    public int readUnsignedShort()
    {
        return readShort() & 0xFFFF;
    }

    @Override
    public int readUnsignedShortLE()
    {
        return readShortLE() & 0xFFFF;
    }

    @Override
    public int readMedium()
    {
        int value = readUnsignedMedium();
        if ((value & 0x800000) != 0)
        {
            value |= 0xff000000;
        }
        return value;
    }

    @Override
    public int readMediumLE()
    {
        int value = readUnsignedMediumLE();
        if ((value & 0x800000) != 0)
        {
            value |= 0xff000000;
        }
        return value;
    }

    @Override
    public int readUnsignedMedium()
    {
        checkReadableBytes0(3);
        int v = _getUnsignedMedium(readerIndex);
        readerIndex += 3;
        return v;
    }

    @Override
    public int readUnsignedMediumLE()
    {
        checkReadableBytes0(3);
        int v = _getUnsignedMediumLE(readerIndex);
        readerIndex += 3;
        return v;
    }

    @Override
    public int readInt()
    {
        checkReadableBytes0(4);
        int v = _getInt(readerIndex);
        readerIndex += 4;
        return v;
    }

    @Override
    public int readIntLE()
    {
        checkReadableBytes0(4);
        int v = _getIntLE(readerIndex);
        readerIndex += 4;
        return v;
    }

    @Override
    public long readUnsignedInt()
    {
        return readInt() & 0xFFFFFFFFL;
    }

    @Override
    public long readUnsignedIntLE()
    {
        return readIntLE() & 0xFFFFFFFFL;
    }

    @Override
    public long readLong()
    {
        checkReadableBytes0(8);
        long v = _getLong(readerIndex);
        readerIndex += 8;
        return v;
    }

    @Override
    public long readLongLE()
    {
        checkReadableBytes0(8);
        long v = _getLongLE(readerIndex);
        readerIndex += 8;
        return v;
    }

    @Override
    public char readChar()
    {
        return (char) readShort();
    }

    @Override
    public float readFloat()
    {
        return Float.intBitsToFloat(readInt());
    }

    @Override
    public double readDouble()
    {
        return Double.longBitsToDouble(readLong());
    }

    @Override
    public ByteBuf readBytes(int length)
    {
        checkReadableBytes(length);
        if (length == 0)
        {
            return Unpooled.EMPTY_BUFFER;
        }

        ByteBuf buf = alloc().buffer(length, maxCapacity);
        buf.writeBytes(this, readerIndex, length);
        readerIndex += length;
        return buf;
    }

    @Override
    public ByteBuf readSlice(int length)
    {
        ByteBuf slice = slice(readerIndex, length);
        readerIndex += length;
        return slice;
    }

    @Override
    public ByteBuf readRetainedSlice(int length)
    {
        ByteBuf slice = retainedSlice(readerIndex, length);
        readerIndex += length;
        return slice;
    }

    @Override
    public ByteBuf readBytes(byte[] dst, int dstIndex, int length)
    {
        checkReadableBytes(length);
        getBytes(readerIndex, dst, dstIndex, length);
        readerIndex += length;
        return this;
    }

    @Override
    public ByteBuf readBytes(byte[] dst)
    {
        readBytes(dst, 0, dst.length);
        return this;
    }

    @Override
    public ByteBuf readBytes(ByteBuf dst)
    {
        readBytes(dst, dst.writableBytes());
        return this;
    }

    @Override
    public ByteBuf readBytes(ByteBuf dst, int length)
    {
        if (length > dst.writableBytes())
        {
            throw new IndexOutOfBoundsException(String.format(
                    "length(%d) exceeds dst.writableBytes(%d) where dst is: %s",
                    length, dst.writableBytes(), dst));
        }
        readBytes(dst, dst.writerIndex(), length);
        dst.writerIndex(dst.writerIndex() + length);
        return this;
    }

    @Override
    public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length)
    {
        checkReadableBytes(length);
        getBytes(readerIndex, dst, dstIndex, length);
        readerIndex += length;
        return this;
    }

    @Override
    public ByteBuf readBytes(ByteBuffer dst)
    {
        int length = dst.remaining();
        checkReadableBytes(length);
        getBytes(readerIndex, dst);
        readerIndex += length;
        return this;
    }

    @Override
    public int readBytes(GatheringByteChannel out, int length)
            throws IOException
    {
        checkReadableBytes(length);
        int readBytes = getBytes(readerIndex, out, length);
        readerIndex += readBytes;
        return readBytes;
    }

    @Override
    public int readBytes(FileChannel out, long position, int length)
            throws IOException
    {
        checkReadableBytes(length);
        int readBytes = getBytes(readerIndex, out, position, length);
        readerIndex += readBytes;
        return readBytes;
    }

    @Override
    public ByteBuf readBytes(OutputStream out, int length) throws IOException
    {
        checkReadableBytes(length);
        getBytes(readerIndex, out, length);
        readerIndex += length;
        return this;
    }

    @Override
    public ByteBuf skipBytes(int length)
    {
        checkReadableBytes(length);
        readerIndex += length;
        return this;
    }

    @Override
    public ByteBuf writeBoolean(boolean value)
    {
        writeByte(value ? 1 : 0);
        return this;
    }

    @Override
    public ByteBuf writeByte(int value)
    {
        ensureAccessible();
        ensureWritable0(1);
        _setByte(writerIndex++, value);
        return this;
    }

    @Override
    public ByteBuf writeShort(int value)
    {
        ensureAccessible();
        ensureWritable0(2);
        _setShort(writerIndex, value);
        writerIndex += 2;
        return this;
    }

    @Override
    public ByteBuf writeShortLE(int value)
    {
        ensureAccessible();
        ensureWritable0(2);
        _setShortLE(writerIndex, value);
        writerIndex += 2;
        return this;
    }

    @Override
    public ByteBuf writeMedium(int value)
    {
        ensureAccessible();
        ensureWritable0(3);
        _setMedium(writerIndex, value);
        writerIndex += 3;
        return this;
    }

    @Override
    public ByteBuf writeMediumLE(int value)
    {
        ensureAccessible();
        ensureWritable0(3);
        _setMediumLE(writerIndex, value);
        writerIndex += 3;
        return this;
    }

    @Override
    public ByteBuf writeInt(int value)
    {
        ensureAccessible();
        ensureWritable0(4);
        _setInt(writerIndex, value);
        writerIndex += 4;
        return this;
    }

    @Override
    public ByteBuf writeIntLE(int value)
    {
        ensureAccessible();
        ensureWritable0(4);
        _setIntLE(writerIndex, value);
        writerIndex += 4;
        return this;
    }

    @Override
    public ByteBuf writeLong(long value)
    {
        ensureAccessible();
        ensureWritable0(8);
        _setLong(writerIndex, value);
        writerIndex += 8;
        return this;
    }

    @Override
    public ByteBuf writeLongLE(long value)
    {
        ensureAccessible();
        ensureWritable0(8);
        _setLongLE(writerIndex, value);
        writerIndex += 8;
        return this;
    }

    @Override
    public ByteBuf writeChar(int value)
    {
        writeShort(value);
        return this;
    }

    @Override
    public ByteBuf writeFloat(float value)
    {
        writeInt(Float.floatToRawIntBits(value));
        return this;
    }

    @Override
    public ByteBuf writeDouble(double value)
    {
        writeLong(Double.doubleToRawLongBits(value));
        return this;
    }

    @Override
    public ByteBuf writeBytes(byte[] src, int srcIndex, int length)
    {
        ensureAccessible();
        ensureWritable(length);
        setBytes(writerIndex, src, srcIndex, length);
        writerIndex += length;
        return this;
    }

    @Override
    public ByteBuf writeBytes(byte[] src)
    {
        writeBytes(src, 0, src.length);
        return this;
    }

    @Override
    public ByteBuf writeBytes(ByteBuf src)
    {
        writeBytes(src, src.readableBytes());
        return this;
    }

    @Override
    public ByteBuf writeBytes(ByteBuf src, int length)
    {
        if (length > src.readableBytes())
        {
            throw new IndexOutOfBoundsException(String.format(
                    "length(%d) exceeds src.readableBytes(%d) where src is: %s",
                    length, src.readableBytes(), src));
        }
        writeBytes(src, src.readerIndex(), length);
        src.readerIndex(src.readerIndex() + length);
        return this;
    }

    @Override
    public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length)
    {
        ensureAccessible();
        ensureWritable(length);
        setBytes(writerIndex, src, srcIndex, length);
        writerIndex += length;
        return this;
    }

    @Override
    public ByteBuf writeBytes(ByteBuffer src)
    {
        ensureAccessible();
        int length = src.remaining();
        ensureWritable(length);
        setBytes(writerIndex, src);
        writerIndex += length;
        return this;
    }

    @Override
    public int writeBytes(InputStream in, int length) throws IOException
    {
        ensureAccessible();
        ensureWritable(length);
        int writtenBytes = setBytes(writerIndex, in, length);
        if (writtenBytes > 0)
        {
            writerIndex += writtenBytes;
        }
        return writtenBytes;
    }

    @Override
    public int writeBytes(ScatteringByteChannel in, int length)
            throws IOException
    {
        ensureAccessible();
        ensureWritable(length);
        int writtenBytes = setBytes(writerIndex, in, length);
        if (writtenBytes > 0)
        {
            writerIndex += writtenBytes;
        }
        return writtenBytes;
    }

    @Override
    public int writeBytes(FileChannel in, long position, int length)
            throws IOException
    {
        ensureAccessible();
        ensureWritable(length);
        int writtenBytes = setBytes(writerIndex, in, position, length);
        if (writtenBytes > 0)
        {
            writerIndex += writtenBytes;
        }
        return writtenBytes;
    }

    @Override
    public ByteBuf writeZero(int length)
    {
        if (length == 0)
        {
            return this;
        }

        ensureWritable(length);
        int wIndex = writerIndex;
        checkIndex(wIndex, length);

        int nLong = length >>> 3;
        int nBytes = length & 7;
        for (int i = nLong; i > 0; i--)
        {
            _setLong(wIndex, 0);
            wIndex += 8;
        }
        if (nBytes == 4)
        {
            _setInt(wIndex, 0);
            wIndex += 4;
        }
        else if (nBytes < 4)
        {
            for (int i = nBytes; i > 0; i--)
            {
                _setByte(wIndex, (byte) 0);
                wIndex++;
            }
        }
        else
        {
            _setInt(wIndex, 0);
            wIndex += 4;
            for (int i = nBytes - 4; i > 0; i--)
            {
                _setByte(wIndex, (byte) 0);
                wIndex++;
            }
        }
        writerIndex = wIndex;
        return this;
    }

    @Override
    public int writeCharSequence(CharSequence sequence, Charset charset)
    {
        int written = setCharSequence(writerIndex, sequence, charset);
        writerIndex += written;
        return written;
    }

    @Override
    public ByteBuf copy()
    {
        return copy(readerIndex, readableBytes());
    }

    @Override
    public ByteBuf duplicate()
    {
        return new UnpooledDuplicatedByteBuf(this);
    }

    @Override
    public ByteBuf retainedDuplicate()
    {
        return duplicate().retain();
    }

    @Override
    public ByteBuf slice()
    {
        return slice(readerIndex, readableBytes());
    }

    @Override
    public ByteBuf retainedSlice()
    {
        return slice().retain();
    }

    @Override
    public ByteBuf slice(int index, int length)
    {
        return new UnpooledSlicedByteBuf(this, index, length);
    }

    @Override
    public ByteBuf retainedSlice(int index, int length)
    {
        return slice(index, length).retain();
    }

    @Override
    public ByteBuffer nioBuffer()
    {
        return nioBuffer(readerIndex, readableBytes());
    }

    @Override
    public ByteBuffer[] nioBuffers()
    {
        return nioBuffers(readerIndex, readableBytes());
    }

    @Override
    public String toString(Charset charset)
    {
        return toString(readerIndex, readableBytes(), charset);
    }

    @Override
    public String toString(int index, int length, Charset charset)
    {
        return ByteBufUtil.decodeString(this, index, length, charset);
    }

    @Override
    public int indexOf(int fromIndex, int toIndex, byte value)
    {
        return ByteBufUtil.indexOf(this, fromIndex, toIndex, value);
    }

    @Override
    public int bytesBefore(byte value)
    {
        return bytesBefore(readerIndex(), readableBytes(), value);
    }

    @Override
    public int bytesBefore(int length, byte value)
    {
        checkReadableBytes(length);
        return bytesBefore(readerIndex(), length, value);
    }

    @Override
    public int bytesBefore(int index, int length, byte value)
    {
        int endIndex = indexOf(index, index + length, value);
        if (endIndex < 0)
        {
            return -1;
        }
        return endIndex - index;
    }

    @Override
    public int forEachByte(ByteProcessor processor)
    {
        ensureAccessible();
        try
        {
            return forEachByteAsc0(readerIndex, writerIndex, processor);
        }
        catch (Exception e)
        {
            PlatformDependent.throwException(e);
            return -1;
        }
    }

    @Override
    public int forEachByte(int index, int length, ByteProcessor processor)
    {
        checkIndex(index, length);
        try
        {
            return forEachByteAsc0(index, index + length, processor);
        }
        catch (Exception e)
        {
            PlatformDependent.throwException(e);
            return -1;
        }
    }

    private int forEachByteAsc0(int start, int end, ByteProcessor processor)
            throws Exception
    {
        for (; start < end; ++start)
        {
            if (!processor.process(_getByte(start)))
            {
                return start;
            }
        }

        return -1;
    }

    @Override
    public int forEachByteDesc(ByteProcessor processor)
    {
        ensureAccessible();
        try
        {
            return forEachByteDesc0(writerIndex - 1, readerIndex, processor);
        }
        catch (Exception e)
        {
            PlatformDependent.throwException(e);
            return -1;
        }
    }

    @Override
    public int forEachByteDesc(int index, int length, ByteProcessor processor)
    {
        checkIndex(index, length);
        try
        {
            return forEachByteDesc0(index + length - 1, index, processor);
        }
        catch (Exception e)
        {
            PlatformDependent.throwException(e);
            return -1;
        }
    }

    private int forEachByteDesc0(int rStart, final int rEnd,
            ByteProcessor processor) throws Exception
    {
        for (; rStart >= rEnd; --rStart)
        {
            if (!processor.process(_getByte(rStart)))
            {
                return rStart;
            }
        }
        return -1;
    }

    @Override
    public int hashCode()
    {
        return ByteBufUtil.hashCode(this);
    }

    @Override
    public boolean equals(Object o)
    {
        return this == o || (o instanceof ByteBuf
                && ByteBufUtil.equals(this, (ByteBuf) o));
    }

    @Override
    public int compareTo(ByteBuf that)
    {
        return ByteBufUtil.compare(this, that);
    }

    @Override
    public String toString()
    {
        if (refCnt() == 0)
        {
            return StringUtil.simpleClassName(this) + "(freed)";
        }

        StringBuilder buf = new StringBuilder()
                .append(StringUtil.simpleClassName(this)).append("(ridx: ")
                .append(readerIndex).append(", widx: ").append(writerIndex)
                .append(", cap: ").append(capacity());
        if (maxCapacity != Integer.MAX_VALUE)
        {
            buf.append('/').append(maxCapacity);
        }

        ByteBuf unwrapped = unwrap();
        if (unwrapped != null)
        {
            buf.append(", unwrapped: ").append(unwrapped);
        }
        buf.append(')');
        return buf.toString();
    }

    protected final void checkIndex(int index)
    {
        checkIndex(index, 1);
    }

    protected final void checkIndex(int index, int fieldLength)
    {
        ensureAccessible();
        checkIndex0(index, fieldLength);
    }

    final void checkIndex0(int index, int fieldLength)
    {
        if (isOutOfBounds(index, fieldLength, capacity()))
        {
            throw new IndexOutOfBoundsException(String.format(
                    "index: %d, length: %d (expected: range(0, %d))", index,
                    fieldLength, capacity()));
        }
    }

    protected final void checkSrcIndex(int index, int length, int srcIndex,
            int srcCapacity)
    {
        checkIndex(index, length);
        if (isOutOfBounds(srcIndex, length, srcCapacity))
        {
            throw new IndexOutOfBoundsException(String.format(
                    "srcIndex: %d, length: %d (expected: range(0, %d))",
                    srcIndex, length, srcCapacity));
        }
    }

    protected final void checkDstIndex(int index, int length, int dstIndex,
            int dstCapacity)
    {
        checkIndex(index, length);
        if (isOutOfBounds(dstIndex, length, dstCapacity))
        {
            throw new IndexOutOfBoundsException(String.format(
                    "dstIndex: %d, length: %d (expected: range(0, %d))",
                    dstIndex, length, dstCapacity));
        }
    }

    /**
     * Throws an {@link IndexOutOfBoundsException} if the current
     * {@linkplain #readableBytes() readable bytes} of this buffer is less than
     * the specified value.
     */
    protected final void checkReadableBytes(int minimumReadableBytes)
    {
        if (minimumReadableBytes < 0)
        {
            throw new IllegalArgumentException("minimumReadableBytes: "
                    + minimumReadableBytes + " (expected: >= 0)");
        }
        checkReadableBytes0(minimumReadableBytes);
    }

    protected final void checkNewCapacity(int newCapacity)
    {
        ensureAccessible();
        if (newCapacity < 0 || newCapacity > maxCapacity())
        {
            throw new IllegalArgumentException("newCapacity: " + newCapacity
                    + " (expected: 0-" + maxCapacity() + ')');
        }
    }

    private void checkReadableBytes0(int minimumReadableBytes)
    {
        ensureAccessible();
        if (readerIndex > writerIndex - minimumReadableBytes)
        {
            throw new IndexOutOfBoundsException(String.format(
                    "readerIndex(%d) + length(%d) exceeds writerIndex(%d): %s",
                    readerIndex, minimumReadableBytes, writerIndex, this));
        }
    }

    /**
     * Should be called by every method that tries to access the buffers content
     * to check if the buffer was released before.
     */
    protected final void ensureAccessible()
    {
        if (checkAccessible && refCnt() == 0)
        {
            throw new IllegalReferenceCountException(0);
        }
    }

    final void setIndex0(int readerIndex, int writerIndex)
    {
        this.readerIndex = readerIndex;
        this.writerIndex = writerIndex;
    }

    final void discardMarks()
    {
        markedReaderIndex = markedWriterIndex = 0;
    }
}
